/* Showdown.c */

#include <nds.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <time.h>

#include "screen.h"

#include "Showdown.h"

Hand_T      board[5], holeHand[kMaxPlayers], deck[52],
            /* The following are each one card per hand so the cards can be
               output to results in the same order that user input them: */
            holeCard[2*kMaxPlayers], boardCard[4+1];
long        wins[kMaxPlayers], splits[kMaxPlayers];
int         nPlayers, nBoardCards, nCardsInDeck, nDeadCards;
double      partialPots[kMaxPlayers], nPots, Results[8];
int         limitIx0, limitIx1, limitIx2, limitIx3, limitIx4;
int         dealt[52];

void InitDeck(void)
{
    Hand_T	*h;
    int		rank, suit, i = 0;

    nDeadCards = 0;
    nBoardCards = 0;
    nPlayers = 0;

    h = deck;
    for (suit = kClubs; suit <= kSpades; ++suit)
        for (rank = 2; rank <= kA; ++rank) {
            (h++)->as64Bits = Encode(rank, suit);
            dealt[i++] = false; }
}

#define mPotResults                                     \
    {                                                   \
    Eval_T			eval, bestEval, handValue[kMaxPlayers]; \
    int				i, winningPlayer = -1, waysSplit;        \
    Hand_T			player;                             \
    double			partialPot;                         \
                                                        \
    bestEval = 0;                                       \
    for (i = 0; i < nPlayers; ++i) {                    \
        CombineHands(player, board[4], holeHand[i]);    \
        handValue[i] = eval = Hand_7_Eval(player);      \
        if (eval > bestEval) {                          \
            bestEval = eval;                            \
            waysSplit = 0;                              \
            winningPlayer = i; }                        \
        else                                            \
            if (eval == bestEval)                       \
                ++waysSplit; }                          \
    if (!waysSplit)                                     \
        ++wins[winningPlayer];                          \
    else {                                              \
        partialPot = 1.0/++waysSplit;                   \
        for (i = 0; waysSplit; ++i)                     \
            if (handValue[i] == bestEval) {             \
                partialPots[i] += partialPot;           \
                ++splits[i];                            \
                --waysSplit; } } }

#define MC_MAX 30000
char randPos[9][MC_MAX][5];

void SetupMonteCarlo(int nHandsDealt)
{
    static bool alreadyDone[8];

    if(alreadyDone[nHandsDealt])
    {
        return;
    }

    int r[5];
    int used[52];
    memset(used, 0, 52 * sizeof(int));

    int cardsAvail = 52 - nHandsDealt * 2;

    for(int g = 0 ; g < MC_MAX ; g++)
    {
        for(int f = 0 ; f < 5 ; )
        {
            r[f] = rand() % cardsAvail;
            if(!used[r[f]])
            {
                used[r[f++]] = 1;
            }
        }
        randPos[nHandsDealt][g][0] = r[0];
        randPos[nHandsDealt][g][1] = r[1];
        randPos[nHandsDealt][g][2] = r[2];
        randPos[nHandsDealt][g][3] = r[3];
        randPos[nHandsDealt][g][4] = r[4];
        used[r[0]] = used[r[1]] = used[r[2]] = used[r[3]] = used[r[4]] = 0;
    }

    alreadyDone[nHandsDealt] = true;
}

void MonteCarloNoFlop(int numAllIn)
{
    int nHandsDealt = (52 - nCardsInDeck) / 2;
    SetupMonteCarlo(nHandsDealt);

    for(int pots = 0 ; pots < MC_MAX ; pots++)
    {
        board[0] = deck[(int)randPos[nHandsDealt][pots][0]];
        CombineHands(board[1], board[0], deck[(int)randPos[nHandsDealt][pots][1]]);
        CombineHands(board[2], board[1], deck[(int)randPos[nHandsDealt][pots][2]]);
        CombineHands(board[3], board[2], deck[(int)randPos[nHandsDealt][pots][3]]);
        CombineHands(board[4], board[3], deck[(int)randPos[nHandsDealt][pots][4]]);

        mPotResults;
    }
    nPots = MC_MAX;
}

void EnumBoardsNoUnknown(void)
{
    int deckIx1, deckIx2, deckIx3, deckIx4;

    switch (nBoardCards) {
        case 0:
            // Use MonteCarlo approach when there are no board cards.
            // Not 100% accurate but way faster than full evaluation
            // and close enough.
            MonteCarloNoFlop(nPlayers);
            break;
        case 1:
            for (deckIx1 = 0; deckIx1 <= limitIx1; ++deckIx1) {
                CombineHands(board[1], board[0], deck[deckIx1]);
                for (deckIx2 = deckIx1 + 1; deckIx2 <= limitIx2; ++deckIx2) {
                    CombineHands(board[2], board[1], deck[deckIx2]);
                    for (deckIx3 = deckIx2 + 1; deckIx3 <= limitIx3; ++deckIx3) {
                        CombineHands(board[3], board[2], deck[deckIx3]);
                        for (deckIx4 = deckIx3 + 1; deckIx4 <= limitIx4; ++deckIx4) {
                            CombineHands(board[4], board[3], deck[deckIx4]);
                            mPotResults } } } }
            break;
        case 2:
            for (deckIx2 = 0; deckIx2 <= limitIx2; ++deckIx2) {
                CombineHands(board[2], board[0], deck[deckIx2]);
                for (deckIx3 = deckIx2 + 1; deckIx3 <= limitIx3; ++deckIx3) {
                    CombineHands(board[3], board[2], deck[deckIx3]);
                    for (deckIx4 = deckIx3 + 1; deckIx4 <= limitIx4; ++deckIx4) {
                        CombineHands(board[4], board[3], deck[deckIx4]);
                        mPotResults } } }
            break;
        case 3:
            for (deckIx3 = 0; deckIx3 <= limitIx3; ++deckIx3) {
                CombineHands(board[3], board[0], deck[deckIx3]);
                for (deckIx4 = deckIx3 + 1; deckIx4 <= limitIx4; ++deckIx4) {
                    CombineHands(board[4], board[3], deck[deckIx4]);
                    mPotResults } }
            break;
        case 4:
            for (deckIx4 = 0; deckIx4 <= limitIx4; ++deckIx4) {
                CombineHands(board[4], board[0], deck[deckIx4]);
                mPotResults }
    } /* end switch */
}

void Enumerate(void) {

    int i;
    int deckIx0 = 0, deckIx1;

    for (i = 0; i < nPlayers; ++i) {
        wins[i] = splits[i] = 0;
        partialPots[i] = 0.0; }

    /* Compact deck array to remove elements for user-specified hole/board/dead cards */
    for (deckIx1 = 0; deckIx1 < 52; ++deckIx1)
        if (!dealt[deckIx1]) {
            deck[deckIx0] = deck[deckIx1];
            dealt[deckIx0++] = false; }

    limitIx4 = nCardsInDeck - 1;
    limitIx3 = limitIx4 - 1;
    limitIx2 = limitIx3 - 1;
    limitIx1 = limitIx2 - 1;
    limitIx0 = limitIx1 - 1;

	EnumBoardsNoUnknown();

    for (int i = 0; i < nPlayers; ++i)
    {
        Results[i] = (wins[i]+splits[i])*100.0/nPots;
    }
}

void SetHoleCards(int rank, int suit, int rank2, int suit2)
{
    rank += 2;  // Difference between two poker engines.
    rank2 += 2;  // Difference between two poker engines.

    holeCard[nPlayers * 2].as64Bits = Encode(rank, suit);
    holeCard[nPlayers * 2 + 1].as64Bits = Encode(rank2, suit2);

    dealt[(suit-kClubs)*13 + rank - 2] = true;
    dealt[(suit2-kClubs)*13 + rank2 - 2] = true;

    CombineHands(holeHand[nPlayers], holeCard[2*nPlayers], holeCard[2*nPlayers + 1]);

    nPlayers++;
}

void SetBoardCard(int rank, int suit)
{
    if(!nBoardCards)
    {
        ZeroHand(board[0]);
    }

    rank += 2;  // Difference between two poker engines.

    boardCard[nBoardCards].as64Bits = Encode(rank, suit);
    dealt[(suit-kClubs)*13 + rank - 2] = true;
    AddHandTo(board[0], boardCard[nBoardCards]);

    nBoardCards++;
}

Boolean GetBoard(int *nBoardCards, Hand_T *board)
{
    ZeroHand(*board);
    *nBoardCards = 3;

    boardCard[0].as64Bits = Encode(kQ, kDiamonds);
    boardCard[1].as64Bits = Encode(6, kHearts);
    boardCard[2].as64Bits = Encode(5, kDiamonds);

    for (int i = 0; i < *nBoardCards; ++i) {
        int rank, suit;

        Decode(boardCard[i], &rank, &suit);
        dealt[(suit-kClubs)*13 + rank - 2] = true;

        AddHandTo(*board, boardCard[i]);
    }

    return true;
}

void KillCard(int rank, int suit)
{
    rank += 2;  // Difference between two poker engines.

    nDeadCards++;
    dealt[(suit-kClubs)*13 + rank - 2] = true;
}

void InitializeForOdds(void)
{
    if (!Init_Hand_Eval()) {
        prints(BOTTOM, 0, 0, "RAM allocation failure (InitForOdds)");
        exit(0); }

    InitDeck();
}

double Factorial(double n)
{
    if (n > 1.0)
        return n * Factorial(n - 1.0);
    return 1.0;
}

double Combos(int chooseFrom, int choose)
{
    return Factorial((double)chooseFrom) /
            (Factorial((double)choose) * Factorial((double)(chooseFrom - choose)));
}

double NbrEnumerations(void)
{
    double  enums;
    enums = Combos(nCardsInDeck, 5 - nBoardCards); /* number of boards */
    return enums;
}

void CalculateHandOdds(void)
{
    nCardsInDeck = 52 - nPlayers * 2 - nBoardCards - nDeadCards;
    nPots = NbrEnumerations();

    Enumerate();
}
